//AvenirSQL的事务实现类
const moment = require('moment');
const { SUCCESS } = require('./common/constants');
const libcu = require('libcu');
const { toLog, EOL } = require('./common/common');
const fs = require('fs');
class Trans {
    constructor() {
        this.transInfo = {};                    //目前的事务信息
        this.transInfo.test = {};               //测试
        this.rollbackTime = ini.db.rollbackTime > ini.db.releaseLockTime ? ini.db.rollbackTime : ini.db.releaseLockTime;
        setInterval(() => {
            this.autoDeleteTransId();
        }, this.rollbackTime / 10 * 1000);
    }

    //关于事务的主要入口函数
    async transParse(userSQL, sign, id) {
        (userSQL.trim() === 'begin') || this.checkTransCode(id)
        // let arr = sql.split(' ');
        if (userSQL.trim() === 'begin') {
            await this.beginTrans(sign);
        } else if (userSQL.trim() === 'commit') {
            toLog("收到提交请求");
            await this.commit(sign, id);

        } else if (userSQL.trim() === 'rollback') {
            toLog("收到回滚请求 ")
            await this.rollback(sign, id);
        } else {
            //解析SQL
            let parsed = sql.parseSql(userSQL);
            await this.dbop(parsed, sign, id);
        }

    }

    //具体思路：
    // 根据事务ID进行事务操作，获取sql表名后加载表数据至内存，随后的操作都在这个缓存中进行
    // commit:缓存内容写文件
    // rollback:清缓存

    //传入签名 生成一个事务版本号
    async beginTrans(sign) {
        toLog("beginTrans");
        let now = moment().valueOf();
        let code = await this.getTransCode(sign);

        //存入事务信息
        this.transInfo[code] = {
            createTime: now,
            operator: {
                //期望存的数据：database：{ table : {tableData,hashIndex,bptData}}
            }
        };
        toLog("当前事务的ID为", code);
        throw ({ code: SUCCESS, data: code });
    }

    async commit(sign, id) {
        //先写数据再解锁
        await this.writeDataAndReleaseLock(sign, id);
        throw (SUCCESS);
    }

    async rollback(sign, id) {
        try {
            await this.releaseLock(sign, id);
            delete this.transInfo[id];

            throw (SUCCESS);

        } catch (error) {
            toLog("rollback error->", error);
            delete this.transInfo[id];
            throw (error);
        }


        // this.autoRollback(id);
        // throw (SUCCESS);
    }

    async dbop(par, sign, id) {
        toLog("par = ", par);
        this.transInfo[id] = this.transInfo[id] || {};
        // transInfo[database] = {
        //     tableName : {
        //         tableData : null,
        //         tableDefine : null,
        //         hashIndex : null,
        //         bptFile : null,
        //         tableDetail : null,
        //     }
        // }
        toLog("this.transInfo = ", this.transInfo);
        toLog("事务ID 为 ", id);
        try {
            //把当前的事务信息传进去
            if (par.type === 'insert') {
                await db.insert(par, sign, true, true, this.transInfo[id]);
            } else if (par.type === 'delete') {
                await db.delete(par, sign, true, true, this.transInfo[id]);
            } else if (par.type === 'select') {
                await db.select(par, sign, true, true, true, this.transInfo[id]);
            } else if (par.type === 'update') {
                await db.update(par, sign, true, true, this.transInfo[id]);
            } else {
                throw ("SQL_NOT_SUPPORT");
            }
        } catch (error) {
            toLog("事务执行error->", error);
            if (error.code == SUCCESS) {
                toLog("事务单条语句执行成功");
                this.transInfo[id] = error.trans;

                if (error.data) {
                    //为了把trans信息隐藏
                    throw {
                        code: error.code,
                        message: error.message,
                        data: error.data,
                    }
                }
            } else {
                await this.rollback(sign, id);
            }
            //20210105 回滚了会报成功所以不需要再throw了
            throw (error);
        }

    }

    //生成一个事务ID
    async getTransCode(sign) {
        let rand = Math.random(sign);
        return rand.toString().slice(3) + sign;
    }

    //判断是否存在该事务ID
    checkTransCode(id) {
        if (!id || !this.transInfo[id]) {
            throw ('TRANS_NOT_FOUND');
        } else {
            //更新创建时间
            this.transInfo[id].createTime = moment().valueOf();
        }
    }

    //当出现问题的时候 自动回滚的函数 不需要验签
    autoRollback(id) {
        toLog("回滚了事务ID ", id);
        delete this.transInfo[id];      //删除事务信息
    }

    //回滚或提交需要解锁
    async releaseLock(sign, id) {
        let trans = this.transInfo[id];
        //遍历 逐一解锁
        for (let database in trans) {
            let tables = trans[database];
            for (let table in tables) {
                await lock.getLock(database, table, 1, sign, null, null);
            }
        }
    }

    //提交事务后将缓存数据写入文件
    async writeDataAndReleaseLock(sign, id) {
        let trans = this.transInfo[id];
        toLog("trans = ", trans);
        //遍历 逐一解锁 写文件
        for (let database in trans) {
            let tables = trans[database];
            for (let table in tables) {
                let data = tables[table];
                let fileNames = data.fileName;
                //有数据就写文件
                if (data.hashIndex) {
                    fs.writeFileSync(fileNames.hashFile, JSON.stringify(data.hashIndex));
                    toLog("写入哈希索引成功")
                }

                if (data.bptFile) {
                    fs.writeFileSync(fileNames.bptFile, JSON.stringify(data.bptFile));
                    toLog("写入B+树索引成功");
                }

                if (data.tableData) {
                    let write = JSON.stringify(data.tableDefine) + EOL;
                    for (let i = 0; i < data.tableData.length; i++) {
                        write += data.tableData[i] + EOL;
                    }
                    fs.writeFileSync(fileNames.tableFile, write);
                    toLog("写入表数据成功");
                }

                await lock.getLock(database, table, 1, sign, null, null);
            }
        }
    }

    autoDeleteTransId() {
        for (let key in this.transInfo) {
            let id = this.transInfo[key];
            let now = moment().valueOf();
            if (moment(now).diff(moment(id.createTime), 'seconds') > ini.db.rollbackTime) {
                toLog("自动删除事务 ", id);
                this.autoRollback(key);
            }
        }
    }
}

module.exports = Trans;
